using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using UnityEngine;
using Cysharp.Threading.Tasks;
using UnityEngine.SceneManagement;
using Net.Unity;
using Net.Helper;
using Net.System;
using Newtonsoft_X.Json;
using Object = UnityEngine.Object;

namespace GameCore
{
    public class AssetPackageManager : IDisposable
    {
        internal readonly Dictionary<string, AssetBundle> assetBundles = new();
        internal readonly MyDictionary<string, AssetBundleLoadAsync> assetBundleAsyncDict = new();
#if UNITY_EDITOR
        private Dictionary<string, string> addressablesDict;//可寻址资源字典,仅用于编辑器模式
#endif
        public List<AssetPackage> Packages { get; set; } = new();

        public Dictionary<string, AssetPackageInfo> LocalPackageDict { get; set; }
        public Dictionary<string, AssetPackageInfo> ServerPackageDict { get; set; }

        public virtual async UniTask Init()
        {
#if UNITY_EDITOR
            if (Global.I.Addressables)
                SearchFilesInFolders();
#endif
            if ((byte)Global.I.Mode <= 1)
                return;
            await InitPackages(Global.Config.AssetBundlePath);
        }

        public async UniTask InitPackages(string assetBundlePath)
        {
            foreach (var assetPackageInfo in ServerPackageDict.Values)
            {
                var file = $"{assetBundlePath}/{assetPackageInfo.Name}.json";
                Debug.Log($"加载包:{file}");
                var bytes = await UnityWebRequestEx.GetAsync(file, request => request.downloadHandler.data, async error => (await UniTask.FromResult(false), default), Global.Config.Timeout);
                if (bytes == null)
                {
                    Debug.LogError($"加载包失败:{file}");
                    continue;
                }
                if (assetPackageInfo.CompressionJson)
                    bytes = UnZipHelper.Decompress(bytes);
                if (assetPackageInfo.EncryptJson)
                    bytes = EncryptHelper.ToDecrypt(Global.Config.Password, bytes);
                var json = Encoding.UTF8.GetString(bytes);
                var assetPackage = JsonConvert.DeserializeObject<AssetPackage>(json);
                assetPackage.PackageInfo = assetPackageInfo;
                InitPackages(assetPackage);
            }
        }

#if UNITY_EDITOR
        public void EditorInitPackages(AssetBundleBuilder assetBundleBuilder)
        {
            var platformPath = $"{assetBundleBuilder.outputPath}/{assetBundleBuilder.config.platform}/";
            var versionPath = $"{platformPath}/{assetBundleBuilder.config.version}/";
            var versionJsonPath = platformPath + "version.json";
            if (!File.Exists(versionJsonPath))
                return;
            var versionJson = File.ReadAllText(versionJsonPath);
            var assetBundleInfos = JsonConvert.DeserializeObject<List<AssetPackageInfo>>(versionJson);
            var assetBundleDict = new Dictionary<string, AssetPackageInfo>();
            foreach (var item in assetBundleInfos)
                assetBundleDict.Add(item.Name, item);
            var files = Directory.GetFiles(versionPath, "*.json");
            foreach (var file in files)
            {
                var packageName = Path.GetFileNameWithoutExtension(file);
                if (!assetBundleDict.TryGetValue(packageName, out var assetPackageInfo))
                    continue;
                var bytes = File.ReadAllBytes(file);
                if (assetPackageInfo.CompressionJson)
                    bytes = UnZipHelper.Decompress(bytes);
                if (assetPackageInfo.EncryptJson)
                    bytes = EncryptHelper.ToDecrypt(assetBundleBuilder.config.password, bytes);
                var json = Encoding.UTF8.GetString(bytes);
                var assetPackage = JsonConvert.DeserializeObject<AssetPackage>(json);
                assetPackage.PackageInfo = assetPackageInfo;
                InitPackages(assetPackage);
            }
        }
#endif

        public void InitPackages(AssetPackage assetPackage)
        {
            assetPackage.PackageManager = this;
            assetPackage.Init();
            Packages.Add(assetPackage);
        }

#if UNITY_EDITOR
        private void SearchFilesInFolders()
        {
            addressablesDict = new Dictionary<string, string>();
            foreach (var package in AssetBundleBuilder.Instance.Packages)
            {
                for (int i = 0; i < package.paths.Count; i++)
                {
                    var path = UnityEditor.AssetDatabase.GetAssetPath(package.paths[i]).Replace("Assets/", "");//获取路径并去掉Assets/，以免路径中重复包含Assets/
                    SearchFilesInFolderRecursive(path, addressablesDict);
                }
            }
        }

        private void SearchFilesInFolderRecursive(string folderPath, Dictionary<string, string> fileDictionary)
        {
            var files = GetAllFilesInFolder(Path.Combine(Application.dataPath, folderPath)); // 获取指定文件夹及其子文件夹中的所有文件
            foreach (var filePath in files)
            {
                var fileName = Path.GetFileNameWithoutExtension(filePath); // 获取文件名（不含后缀）
                var assetPath = filePath.Substring(filePath.IndexOf("Assets")); // 获取相对于Assets文件夹的路径（包含后缀）
                if (!fileDictionary.ContainsKey(fileName))
                    fileDictionary.Add(fileName, assetPath);
            }
        }

        private List<string> GetAllFilesInFolder(string folderPath)
        {
            var allFiles = new List<string>();
            var files = Directory.GetFiles(folderPath);
            foreach (string filePath in files)
            {
                if (!filePath.EndsWith(".meta")) // 过滤掉.meta文件
                    allFiles.Add(filePath);
            }
            var subFolders = Directory.GetDirectories(folderPath);
            foreach (string subFolder in subFolders)
            {
                var subFolderFiles = GetAllFilesInFolder(subFolder);
                allFiles.AddRange(subFolderFiles);
            }
            return allFiles;
        }
#endif

        protected AssetPackage GetAssetPackage(string assetPath)
        {
            AssetPackage package;
            for (int i = 0; i < Packages.Count; i++)
            {
                package = Packages[i];
                if (!package.ContainsAssetInfo(assetPath))
                    continue;
                return package;
            }
            Global.Logger.LogError($"资源加载失败:{assetPath}");
            return null;
        }

        public virtual async UniTask<byte[]> LoadAssetFileAsync(string assetPath, bool isEncrypt = false, bool isAsync = true)
        {
            return await AssetPackage.LoadAssetFileAsync(assetPath, isEncrypt, isAsync);
        }

        /// <summary>
        /// 加载资源，遍历所有资源进行查找尝试加载资源， 如果成功则直接返回
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="assetPath"></param>
        /// <returns></returns>
        public virtual T LoadAsset<T>(string assetPath) where T : Object => LoadAssetAsync<T>(assetPath, false).GetAwaiter().GetResult();

        public virtual async UniTask<T> LoadAssetAsync<T>(string assetPath, bool isAsync = true) where T : Object
        {
#if UNITY_EDITOR
            if (Global.I.Mode == AssetBundleMode.EditorMode)
                return LoadAssetAtPath<T>(assetPath);
#endif
            if (Global.I.Mode == AssetBundleMode.ResourceMode)
                return ResourcesLoad<T>(assetPath);

            var assetPackage = GetAssetPackage(assetPath);
            return await assetPackage.LoadAssetAsync<T>(assetPath, isAsync);
        }

        public virtual T[] LoadAssetWithSubAssets<T>(string assetPath) where T : Object
            => LoadAssetWithSubAssetsAsync<T>(assetPath, false).GetAwaiter().GetResult();

        public virtual async UniTask<T[]> LoadAssetWithSubAssetsAsync<T>(string assetPath, bool isAsync = true) where T : Object
        {
#if UNITY_EDITOR
            if (Global.I.Mode == AssetBundleMode.EditorMode)
                return LoadAllAssetsAtPath<T>(assetPath);
#endif
            if (Global.I.Mode == AssetBundleMode.ResourceMode)
                return ResourcesLoadAll<T>(assetPath);
            var assetPackage = GetAssetPackage(assetPath);
            return await assetPackage.LoadAssetWithSubAssetsAsync<T>(assetPath, isAsync);
        }

#if UNITY_EDITOR
        private T LoadAssetAtPath<T>(string assetPath) where T : Object
        {
            if (Global.I.Addressables)
                addressablesDict.TryGetValue(assetPath, out assetPath);
            Object assetObject;
            if (typeof(T) == typeof(Sprite))
                assetObject = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, typeof(Sprite));
            else
                assetObject = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, typeof(Object));
            return AssetPackage.GetAsset<T>(assetObject);
        }

        private T[] LoadAllAssetsAtPath<T>(string assetPath) where T : Object
        {
            if (Global.I.Addressables)
                addressablesDict.TryGetValue(assetPath, out assetPath);
            var assetObjects = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(assetPath);
            return AssetPackage.ConvertObjects<T>(assetObjects);
        }
#endif

        private T ResourcesLoad<T>(string assetPath) where T : Object
        {
            assetPath = GetResourcesPath(assetPath);
            return Resources.Load<T>(assetPath);
        }

        private T[] ResourcesLoadAll<T>(string assetPath) where T : Object
        {
            assetPath = GetResourcesPath(assetPath);
            return Resources.LoadAll<T>(assetPath);
        }

        private string GetResourcesPath(string assetPath)
        {
            var index = assetPath.IndexOf("Resources");
            if (index == -1)
            {
                Global.Logger.LogError($"加载的资源必须在Resources文件夹下, 不能在其他文件夹:{assetPath}");
                return assetPath;
            }
            assetPath = assetPath.Remove(0, index + 10); //移除前面路径如果有
            index = assetPath.LastIndexOf(".");
            if (index >= 0)
                assetPath = assetPath.Remove(index, assetPath.Length - index); //移除后缀名如果有
            return assetPath;
        }

        public bool TryGetAssetInfo(string assetPath, out AssetInfo assetInfo)
        {
            foreach (var package in Packages)
            {
                if (package.AssetInfoDict.TryGetValue(assetPath, out assetInfo))
                    return true;
            }
            assetInfo = null;
            Global.Logger.LogError($"加载资源:{assetPath}失败!");
            return false;
        }

        public virtual bool LoadAssetScene(string assetPath, LoadSceneMode mode = LoadSceneMode.Single)
        {
            var assetPackage = GetAssetPackage(assetPath);
            return assetPackage.LoadAssetScene(assetPath, mode);
        }

        public virtual async UniTask LoadAssetSceneAsync(string assetPath, Action onLoadComplete = null, LoadSceneMode mode = LoadSceneMode.Single)
        {
            await LoadAssetSceneAsync(assetPath, mode, (progress) => Global.UI.Loading.ShowUI("加载场景中..." + (progress * 100f).ToString("f0") + "%", progress), () =>
            {
                onLoadComplete?.Invoke();
                Global.UI.Loading.HideUI();
            });
        }

        public virtual async UniTask LoadAssetSceneAsync(string assetPath, LoadSceneMode mode = LoadSceneMode.Single, Action<float> progress = null, Action onLoadComplete = null)
        {
            assetPath = AssetPackage.GetAssetPath(assetPath);
            if ((byte)Global.I.Mode <= 1)
            {
                assetPath = Path.GetFileNameWithoutExtension(assetPath);
                goto J;
            }
            var assetPackage = GetAssetPackage(assetPath);
            var assetBundle = await assetPackage.GetAssetBundleAsync(assetPath);
            if (assetBundle == null)
                return;
            J:
            progress?.Invoke(0f);
            await UniTask.Yield();
            var asyncOper = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(assetPath, mode);
            asyncOper.allowSceneActivation = false;
            while (asyncOper.progress < 0.9f)
            {
                progress?.Invoke(asyncOper.progress);
                await UniTask.Yield();
            }
            progress?.Invoke(1f);
            await UniTask.Delay(1000);
            asyncOper.allowSceneActivation = true;
            await UniTask.Yield();
            onLoadComplete?.Invoke();
        }

        public T Instantiate<T>(string assetPath, Transform parent = null) where T : Object
        {
            return Instantiate<T>(assetPath, Vector3.zero, Quaternion.identity, parent);
        }

        public async UniTask<T> InstantiateAsync<T>(string assetPath, Transform parent = null, bool isAsync = true) where T : Object
        {
            return await InstantiateAsync<T>(assetPath, Vector3.zero, Quaternion.identity, parent, isAsync);
        }

        public T Instantiate<T>(string assetPath, Vector3 position, Quaternion rotation, Transform parent = null) where T : Object
        {
            return InstantiateAsync<T>(assetPath, position, rotation, parent, false).GetAwaiter().GetResult();
        }

        public async UniTask<T> InstantiateAsync<T>(string assetPath, Vector3 position, Quaternion rotation, Transform parent = null, bool isAsync = true) where T : Object
        {
            var assetObj = await LoadAssetAsync<T>(assetPath, isAsync);
            if (assetObj == null)
            {
                Global.Logger.LogError($"资源加载失败:{assetPath}");
                return null;
            }
            return Object.Instantiate(assetObj, position, rotation, parent);
        }

        public async UniTaskVoid InstantiateAsync<T>(string assetPath, Action<T> onCompleted) where T : Object
        {
            onCompleted(await InstantiateAsync<T>(assetPath));
        }

        public AssetBundleInfo GetAssetBundleInfo(string assetBundleName)
        {
            for (int i = 0; i < Packages.Count; i++)
            {
                if (Packages[i].AssetBundleDict.TryGetValue(assetBundleName, out var assetBundleInfo))
                    return assetBundleInfo;
            }
            return null;
        }

        public bool ContainsAssetInfo(string assetName)
        {
            lock (this)
            {
                foreach (var package in Packages)
                {
                    if (package.AssetInfoDict.ContainsKey(assetName))
                        return true;
                }
                return false;
            }
        }

        public int GetAssetInfoCount()
        {
            int count = 0;
            foreach (var package in Packages)
                count += package.AssetInfos.Count;
            return count;
        }

        public void Dispose()
        {
            foreach (var assetBundle in assetBundles.Values)
            {
                if (assetBundle == null)
                    continue;
                assetBundle.Unload(true);
            }
            assetBundles.Clear();
        }

        public async UniTask<AssetPackage> CheckDownloadPackage(string packageName)
        {
            Global.UI.Loading.ShowUI("正在检查资源中...", 0f);
            var downloadAssetBundleInfo = await Global.Resources.PackageManager.GetPackageUpdateInfo(packageName);
            if (downloadAssetBundleInfo == null)
                return default;
            if (downloadAssetBundleInfo.fileSize > 0)
            {
                var msgResult = await DownloadManager.DownloadRequestTips("资源请求", $"有资源需要更新:{ByteHelper.ToString(downloadAssetBundleInfo.fileSize)}");
                if (!msgResult)
                    return default;
            }
            var currSize = 0L;
            var totalSize = downloadAssetBundleInfo.fileSize;
            foreach (var assetBundleInfo in downloadAssetBundleInfo.assetBundleUpdateInfos)
            {
                var serverAssetBundleUrl = Global.Config.GetAssetBundleUrl(assetBundleInfo);
                var localAssetBundleUrl = Global.Config.GetAssetBundlePath(assetBundleInfo);
                var isComplete = await DownloadManager.DownloadFile(serverAssetBundleUrl, localAssetBundleUrl, assetBundleInfo.Name, currSize, totalSize);
                if (!isComplete)
                    return default;
                currSize += assetBundleInfo.FileSize;
            }
            WriteJsonFile(downloadAssetBundleInfo.assetPackage);
            Global.UI.Loading.HideUI();
            return downloadAssetBundleInfo.assetPackage;
        }

        public async UniTask<AssetPackage> GetLocalPackageInfo(string packageName)
        {
            if (!LocalPackageDict.TryGetValue(packageName, out var assetPackageInfo))
                return new AssetPackage();
            return await GetLocalPackageInfo(assetPackageInfo);
        }

        public async UniTask<AssetPackage> GetServerPackageInfo(string packageName)
        {
            if (!ServerPackageDict.TryGetValue(packageName, out var assetPackageInfo))
                return new AssetPackage();
            return await GetServerPackageInfo(assetPackageInfo);
        }

        public async UniTask<AssetPackage> GetLocalPackageInfo(AssetPackageInfo assetPackageInfo)
        {
            var localPackageUrl = Global.Config.GetVersionPath($"{assetPackageInfo.Name}.json");
            var assetPackage = await GetAssetBundleInfos(assetPackageInfo, localPackageUrl, false);
            assetPackage ??= new AssetPackage();
            assetPackage.PackageInfo = assetPackageInfo;
            return assetPackage;
        }

        public async UniTask<AssetPackage> GetServerPackageInfo(AssetPackageInfo assetPackageInfo)
        {
            var serverPackageUrl = Global.Config.GetVersionUrl($"{assetPackageInfo.Name}.json");
            var assetPackage = await GetAssetBundleInfos(assetPackageInfo, serverPackageUrl, true);
            if (assetPackage != null)
                assetPackage.PackageInfo = assetPackageInfo;
            return assetPackage;
        }

        public async UniTask<AssetBundleUpdateInfo> GetPackageUpdateInfo(string packageName)
        {
            LocalPackageDict.TryGetValue(packageName, out var localPackageInfo);
            ServerPackageDict.TryGetValue(packageName, out var serverPackageInfo);
            if (localPackageInfo != null && serverPackageInfo != null && localPackageInfo.MD5 == serverPackageInfo.MD5)
                return null;
            var localAssetPackage = await GetLocalPackageInfo(localPackageInfo);
            var serverAssetPackage = await GetServerPackageInfo(serverPackageInfo);
            if (serverAssetPackage == null)
                return null;
            localAssetPackage.Init();
            serverAssetPackage.Init();
            WriteJsonFile(serverAssetPackage);
            var totalSize = 0L;
            var updateAssetBundleInfos = new List<AssetBundleInfo>();
            foreach (var assetBundleInfo in serverAssetPackage.AssetBundleInfos)
            {
                assetBundleInfo.assetPackage = serverAssetPackage;
                if (localAssetPackage.AssetBundleDict.TryGetValue(assetBundleInfo.Name, out var assetBundleInfo1))
                {
                    if (assetBundleInfo1.MD5 != assetBundleInfo.MD5) //如果json包需要你更新, 就不需要检查文件MD5了, 强制更新
                        goto J;
                    if (!Global.Config.CheckFileMD5) //不需要检查文件MD5码
                        continue;
                    var localPath = assetBundleInfo1.AssetBundlePath;
                    if (!File.Exists(localPath))
                        goto J;
                    var md5 = EncryptHelper.ToMD5(localPath);
                    if (md5 == assetBundleInfo.MD5)
                        continue;
                }
            J:
                updateAssetBundleInfos.Add(assetBundleInfo);
                totalSize += assetBundleInfo.FileSize;
            }
            var downloadAssetBundleInfo = new AssetBundleUpdateInfo
            {
                fileSize = totalSize,
                assetPackage = serverAssetPackage,
                assetBundleUpdateInfos = updateAssetBundleInfos
            };
            return downloadAssetBundleInfo;
        }

        public void WriteJsonFile(AssetPackage assetPackage)
        {
            WriteJsonFile(Global.Config.GetVersionPath($"{assetPackage.Name}.json"), assetPackage);
        }

        public void WriteJsonFile(string filePath, AssetPackage assetPackage)
        {
            var json = JsonConvert.SerializeObject(assetPackage, Formatting.Indented);
            var jsonBytes = Encoding.UTF8.GetBytes(json);
            if (assetPackage.PackageInfo.EncryptJson)
                jsonBytes = EncryptHelper.ToEncrypt(Global.Config.Password, jsonBytes);
            if (assetPackage.PackageInfo.CompressionJson)
                jsonBytes = UnZipHelper.Compress(jsonBytes);
            var path = Path.GetDirectoryName(filePath);
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            File.WriteAllBytes(filePath, jsonBytes);
        }

        public void WriteVersionJsonFile(string filePath, object value)
        {
            var json = JsonConvert.SerializeObject(value, Formatting.Indented);
            var jsonBytes = Encoding.UTF8.GetBytes(json);
            var path = Path.GetDirectoryName(filePath);
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
            File.WriteAllBytes(filePath, jsonBytes);
        }

        private async UniTask<AssetPackage> GetAssetBundleInfos(AssetPackageInfo assetPackageInfo, string url, bool showMsg)
        {
            return await UnityWebRequestEx.GetAsync(url, request =>
            {
                var jsonBytes = request.downloadHandler.data;
                if (assetPackageInfo.CompressionJson)
                    jsonBytes = UnZipHelper.Decompress(jsonBytes);
                if (assetPackageInfo.EncryptJson)
                    EncryptHelper.ToDecrypt(Global.Config.Password, jsonBytes);
                var text = Encoding.UTF8.GetString(jsonBytes);
                return JsonConvert.DeserializeObject<AssetPackage>(text);
            },
            async error =>
            {
                return !showMsg ? (false, default) : (await Global.UI.Message.ShowUIAsync("资源请求", "资源请求失败,是否重试?"), default);
            }, Global.Config.Timeout);
        }

        public void Update()
        {
            if (assetBundleAsyncDict.Count <= 0)
                return;
            for (int i = 0; i < assetBundleAsyncDict.count; i++)
            {
                var entry = assetBundleAsyncDict.entries[i];
                if (entry.hashCode == -1)
                    continue;
                if (Time.realtimeSinceStartup < entry.value.ReleaseTime)
                    continue;
                assetBundleAsyncDict.Remove(entry.key);
            }
        }
    }
}